package com.cf.wal.service;

import com.cf.constant.EnumReturnCode;
import com.cf.exception.BizException;
import com.cf.exception.BizStackException;
import com.cf.id.SnowflakeId;
import com.cf.util.ObjectJsonUtil;
import com.cf.wal.dao.WalDao;
import com.cf.wal.model.EnumWalState;
import com.cf.wal.model.Wal;
import com.github.pagehelper.PageHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * boot-awm-WalService
 *
 * @author <a href="2428922347@qq.com">Zhu mingming</a>
 * @info
 * @since 2018/7/4 下午 04:42
 */
@Slf4j
public class WalService implements InitializingBean {

    @Autowired
    private SnowflakeId id;

    @Autowired
    private WalDao walDao;

    private Map<String, WalEngine> engineMap = new HashMap();

    @Autowired
    private List<WalEngine> logEngines;

    /**
     * 记录一个wal日志
     *
     * @param command
     */
    @Transactional
    public long doTry(String command) {
        long id = this.id.nextId();
        Wal wal = new Wal();
        wal.setId(id);
        wal.setSort(id);
        wal.setCommand(command);

        WalEngine logEngine = getLogEngine(wal);
        CommandAction action = ObjectJsonUtil.toObject(command, CommandAction.class);

        //正向操作
        List<Action> redo = logEngine.redo(action);
        //反向数据
        List<Action> undo = logEngine.undo(action);

        wal.setState(EnumWalState.PREPARE.getState());
        wal.setRedo(ObjectJsonUtil.toString(redo));
        wal.setUndo(ObjectJsonUtil.toString(undo));
        walDao.insert(wal);
        return id;
    }

    /**
     * 获取符合条件的LogEngine
     *
     * @param wal
     * @return
     */
    private WalEngine getLogEngine(Wal wal) {
        CommandAction action = ObjectJsonUtil.toObject(wal.getCommand(), CommandAction.class);
        WalEngine logEngine = engineMap.get(action.getType());
        if (logEngine == null) {
            throw new BizException(EnumReturnCode.ERROR.getCode(), "未定义合适的解析器！");
        }
        return logEngine;
    }

    /**
     * 提交，持久化数据
     */
    @Transactional
    public void doCommit(Wal wal) {
        if (wal == null) {
            return;
        }
        WalEngine logEngine = getLogEngine(wal);
        //执行操作
        try {
            logEngine.exec(ObjectJsonUtil.toListObject(wal.getRedo(), Action.class));
            wal.setState(EnumWalState.COMMIT.getState());
            walDao.updateByPrimaryKey(wal);
        } catch (Exception e) {
            logEngine.exec(ObjectJsonUtil.toListObject(wal.getUndo(), Action.class));
            throw new BizStackException(EnumReturnCode.ERROR.getCode(), "操作执行失败，执行回滚操作，删除wal日志", e);
        }
    }

    @Transactional
    public void doCommit(long id) {
        Wal wal = walDao.selectByPrimaryKey(id);
        doCommit(wal);
    }

    @Transactional
    public void doCancel(long id) {
        log.info("开始执行回滚操作！回滚点【{}】", id);
        Wal wal = walDao.selectByPrimaryKey(id);
        doCancel(wal);
    }

    @Transactional
    public void doCancel(Wal wal) {
        if (wal != null) {
            getLogEngine(wal).exec(ObjectJsonUtil.toListObject(wal.getUndo(), Action.class));
            wal.setState(EnumWalState.PREPARE.getState());
            walDao.updateByPrimaryKey(wal);
            return;
        }
        log.warn("回滚点不存在！【{}】", id);
    }

    /**
     * 更具要回滚的结束点，总最新的数据开始回滚！
     *
     * @param id
     */
    public void rollback(long id) {
        Wal wal = walDao.selectByPrimaryKey(id);
        if (wal == null)
            throw new BizException(EnumReturnCode.ERROR.getCode(), "回滚点不存在！【" + id + "】");
        PageHelper.orderBy(" sort desc ");
        List<Wal> wals = walDao.selectBySortBigger(wal.getSort());
        if (wal != null && wals.size() > 0) {
            wals.forEach(wal1 -> {
                doCancel(wal1);
            });
        }
    }

    /**
     * 重启自检
     */
    public void initCheck() {
        PageHelper.orderBy(" sort asc ");
        List<Wal> wals = walDao.selectByState(EnumWalState.PREPARE.getState());

        if (wals != null && wals.size() > 0) {
            log.info("进行自检操作，发现未提交的数据！【{}】", wals.size());
            wals.forEach(wal -> {
                try {
                    doCommit(wal);
                } catch (Exception e) {
                    log.error("执行initCheck 错误【{}】", wal);
                    throw new BizStackException(EnumReturnCode.ERROR.getCode(), "执行initCheck 错误", e);
                }
            });
        } else {
            log.info("进行自检操作，未发现未提交的数据！");
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        logEngines.forEach(le -> {
            engineMap.put(le.supportType(), le);
        });
    }

}
